export function $(selector) {
  return document.querySelector(selector);
}
const components = ["VText", "RectShape", "CircleShape"];
export function isPreventDrop(component) {
  return !components.includes(component) && !component.startsWith("SVG");
}
export const getAssetsFile = (name: string) => {
  if (name) return new URL(`../assets/icon/${name}.png`, import.meta.url).href;
};
export const getItemVal = val => {
  let reg = /^-?\d+.?\d{1,3}/;

  try {
    if (val && val.match) {
      let res = val.match(reg);
      if (res && res.length > 0) {
        res = res[0];
      } else {
        res = val;
      }
      if (res === "0.000") res = 0;
      return res;
    }
  } catch (error) {
    return val;
  }
  return val;
  // return !isNaN(Number(val)) ? Number(val).toFixed(1) : val;
};
export const convertStringToNumber = value => {
  // 检查value是否为字符串类型
  if (typeof value === "string" && value.trim() !== "") {
    // 尝试将字符串去除空格后转换为数字
    const num = parseFloat(value.trim());
    // 检查转换后的数字是否为有效的数字（不是NaN）
    if (!isNaN(num)) {
      // 如果是有效的数字，返回保留三位小数的新数字
      return Number(num.toFixed(3));
    }
  }
  // 如果转换失败或传入的值不是字符串，返回原值
  return value;
};
// export function formatTimestamp(timestamp: number | string, format: string = "YYYY-MM-DD HH:mm:ss") {
//   const date = new Date(timestamp);
//   const year = date.getFullYear();
//   const month = String(date.getMonth() + 1).padStart(2, "0");
//   const day = String(date.getDate()).padStart(2, "0");
//   const hours = String(date.getHours()).padStart(2, "0");
//   const minutes = String(date.getMinutes()).padStart(2, "0");
//   const seconds = String(date.getSeconds()).padStart(2, "0");

//   return format.replace("YYYY", year).replace("MM", month).replace("DD", day).replace("HH", hours).replace("mm", minutes).replace("ss", seconds);
// }
export function filterArrayByUniqueKey(arr, key) {
  if (!arr) return [];
  const seen = new Set();
  const result = [];

  for (const item of arr) {
    if (key === null || item[key] === null || typeof item[key] == "undefined") {
      // 如果key为null或者对象中key的值为null，则保留该对象
      result.push(item);
    } else if (!seen.has(item[key])) {
      // 如果key的值尚未出现过，则保留该对象，并将其值加入到seen集合中
      seen.add(item[key]);
      result.push(item);
    }
  }

  return result;
}
export function isExcluded(exclude, target) {
  // 如果target数组为空，视为不包含任何项，直接返回true
  if (target.length === 0) {
    return true;
  }

  // 将exclude数组转换为Set提高查找效率
  const excludeSet = new Set(exclude);

  // 检查target中是否有任何一项存在于exclude中
  for (const item of target) {
    if (excludeSet.has(item)) {
      return false;
    }
  }

  return true;
}
export function isObjEmpty(obj) {
  // 排除非对象和 null
  if (obj === null || typeof obj !== "object") return true;

  // 优先用 Object.keys（性能最优）
  if (Object.keys(obj).length !== 0) return false;

  // 补充检查 Symbol 和不可枚举属性
  return Object.getOwnPropertySymbols(obj).length === 0 && Object.getOwnPropertyNames(obj).length === 0;
}
/**
 * 将字符串数组统一补全 .png 后缀
 * @param target  原始字符串数组
 * @param ext     要检查 / 补全的扩展名 (默认 ".png")
 * @returns       新的字符串数组（保持纯函数，不修改原数组）
 */
export function ensurePngSuffix(target: string[], ext: string = ".png"): string[] {
  if (!Array.isArray(target)) {
    console.error("ensurePngSuffix: target 必须是字符串数组");
    return [];
  }

  return target.map(str => {
    // 若字符串本身已以 ext 结尾，则直接返回；否则补全
    return str.endsWith(ext) ? str : `${str}${ext}`;
  });
}

/**
 * 从 localStorage 中读取数据，自动校验是否已过期
 * @param {string} key             - 读取的键名
 * @param {*} defaultValue = null  - 读取失败或已过期时返回的默认值
 * @returns {*}                     - 如果未过期返回存储值，否则删除并返回 defaultValue
 */
export function getWithExpiry(key, defaultValue = null) {
  const itemStr = localStorage.getItem(key);
  if (!itemStr) {
    return defaultValue;
  }
  try {
    const item = JSON.parse(itemStr);
    // 校验数据结构是否符合预期
    if (typeof item !== "object" || item === null || !("value" in item) || !("expiry" in item) || typeof item.expiry !== "number") {
      // 数据结构不符合预期，删除并返回默认值
      localStorage.removeItem(key);
      return defaultValue;
    }
    if (Date.now() > item.expiry) {
      localStorage.removeItem(key);
      return defaultValue;
    }
    return item.value;
  } catch (e) {
    // JSON 解析失败，删除并返回默认值
    localStorage.removeItem(key);
    return defaultValue;
  }
}

/**
 * 存储带过期时间的数据到 localStorage
 * @param {string} key - 存储的键名
 * @param {*} value - 要存储的值（可以是任意类型）
 * @param {number} ttl - 过期时间长度
 * @param {'h' | 'm'} unit - 时间单位，'h' 表示小时（默认），'m' 表示分钟
 */
export function setWithExpiry(key, value, ttl, unit = "h") {
  const now = Date.now();
  let ttlMs;

  if (unit === "m") {
    ttlMs = ttl * 60 * 1000;
  } else {
    ttlMs = ttl * 60 * 60 * 1000;
  }

  const item = {
    value,
    expiry: now + ttlMs
  };

  localStorage.setItem(key, JSON.stringify(item));
}

/* ---------- 类型声明 ---------- */

type TimeUnit = "h" | "m"; // 支持的时间单位：小时 / 分钟

interface StoredData<T = unknown> {
  // IndexedDB 内实际存储的数据结构
  key: string; // 主键 —— 对象仓库 keyPath
  value: T; // 业务层真正的数据
  expiry: number; // 过期时间戳（毫秒）
}

/* ---------- 基础配置 ---------- */

const DB_NAME = "MyStorageDB"; // 数据库名称
const STORE_NAME = "KeyValueStore"; // 对象仓库名称
const DB_VERSION = 1; // 数据库版本号

/* ---------- 打开 / 创建数据库 ---------- */
/**
 * 通过 indexedDB.open 打开（如无则创建）数据库与对象仓库
 * 原生 IDBTransaction/IDBOpenDBRequest 均以事件回调形式通知结果
 */
function openDB(): Promise<IDBDatabase> {
  return new Promise((resolve, reject) => {
    const request: IDBOpenDBRequest = indexedDB.open(DB_NAME, DB_VERSION); // :contentReference[oaicite:0]{index=0}

    request.onupgradeneeded = event => {
      const db = (event.target as IDBOpenDBRequest).result;
      // 第一次打开时为当前数据库创建对象仓库
      if (!db.objectStoreNames.contains(STORE_NAME)) {
        db.createObjectStore(STORE_NAME, { keyPath: "key" }); // :contentReference[oaicite:1]{index=1}
      }
    };

    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
}

/* ---------- 写入数据（带 TTL） ---------- */
/**
 * @param key   键名
 * @param value 要保存的业务数据
 * @param ttl   时间长度，默认 1
 * @param unit  时间单位 'h' 或 'm'，默认 'h'
 */
export async function setItem<T>(key: string, value: T, ttl: number = 1, unit: TimeUnit = "h"): Promise<void> {
  const db = await openDB();
  const tx = db.transaction(STORE_NAME, "readwrite");
  const store = tx.objectStore(STORE_NAME);

  const ttlMs = unit === "m" ? ttl * 60_000 : ttl * 3_600_000; // 分钟→毫秒 / 小时→毫秒
  const data: StoredData<T> = { key, value, expiry: Date.now() + ttlMs };

  store.put(data); // put 可“新增或覆盖” :contentReference[oaicite:2]{index=2}

  // 原生 IndexedDB 无 tx.done，因此使用 oncomplete/onerror/onabort 来封装 Promise :contentReference[oaicite:3]{index=3}
  await new Promise<void>((resolve, reject) => {
    tx.oncomplete = () => resolve();
    tx.onerror = () => reject(tx.error);
    tx.onabort = () => reject(tx.error);
  });
}

/* ---------- 读取数据（自动过期校验 & 清理） ---------- */
/**
 * @param key          键名
 * @param defaultValue 若不存在 / 过期 / 数据结构非法时返回
 */
export async function getItem<T>(key: string, defaultValue: T | null = null): Promise<T | null> {
  const db = await openDB();
  const tx = db.transaction(STORE_NAME, "readonly");
  const store = tx.objectStore(STORE_NAME);

  return new Promise<T | null>(resolve => {
    const req = store.get(key);

    req.onsuccess = () => {
      const record = req.result as StoredData<T> | undefined;

      // 情况 1：根本没有这条记录或结构非法 —— 直接返回默认值
      if (!record || typeof record !== "object" || typeof record.expiry !== "number") {
        resolve(defaultValue);
        return;
      }

      // 情况 2：已过期 —— 先删除再返回默认值
      if (Date.now() > record.expiry) {
        const delTx = db.transaction(STORE_NAME, "readwrite");
        delTx.objectStore(STORE_NAME).delete(key);
        delTx.oncomplete = () => resolve(defaultValue);
        delTx.onerror = () => resolve(defaultValue);
        delTx.onabort = () => resolve(defaultValue);
      } else {
        // 情况 3：有效 —— 返回真实数据
        resolve(record.value);
      }
    };

    // 读取失败同样返回默认值
    req.onerror = () => resolve(defaultValue);
  });
}

/* ---------- 删除指定键 ---------- */
export async function removeItem(key: string): Promise<void> {
  const db = await openDB();
  const tx = db.transaction(STORE_NAME, "readwrite");
  tx.objectStore(STORE_NAME).delete(key);

  await new Promise<void>((resolve, reject) => {
    tx.oncomplete = () => resolve();
    tx.onerror = () => reject(tx.error);
    tx.onabort = () => reject(tx.error);
  });
}
